Hygienic macro

Hygienic macros are macros whose expansion is guaranteed not to cause collisions with existing symbol definitions. They are a feature of programming languages such as Scheme and Dylan.

Contents

The hygiene problem

In a programming language that has unhygienic macros, it is possible for existing variable bindings to be hidden from a macro by variable bindings that are created during its expansion. In C, this problem can be illustrated by the following fragment:

#define INCI(i) {int a=0; ++i;}
int main(void)
{
    int a = 0, b = 0;
    INCI(a);
    INCI(b);
    printf("a is now %d, b is now %d\n", a, b);
    return 0;
}

Running the above through the C preprocessor produces:

int main(void)
{
    int a = 0, b = 0;
    {int a=0; ++a;};
    {int a=0; ++b;};
    printf("a is now %d, b is now %d\n", a, b);
    return 0;
}

So the variable a declared in the top scope is never altered by the execution of the program, as the output of the compiled program shows:

a is now 0, b is now 1

Note that some C compilers, such as gcc, have an option like -Wshadow that warns when a local variable shadows a global variable, which would have caught the above problem. The simplest and least robust solution is to give the macro's variables unique names:

#define INCI(i) {int INCIa=0; ++i;}
int main(void)
{
    int a = 0, b = 0;
    INCI(a);
    INCI(b);
    printf("a is now %d, b is now %d\n", a, b);
    return 0;
}

Until a variable named INCIa is created, this solution produces the correct output:

a is now 1, b is now 1

The "hygiene problem" can extend beyond variable bindings. Consider this Common Lisp macro:

(defmacro my-unless (condition &body body)
 `(if (not ,condition)
    (progn
      ,@body)))

While there are no references to variables in this macro, it assumes the symbols "if", "not", and "progn" are all bound to their usual function definitions. If, however the above macro is used in the following code:

(flet ((not (x) x))
  (my-unless t
    (format t "This should not be printed!")))

Because the definition of "not" has been locally altered, the message "This should not be printed!" will be printed, which is probably not the intended behavior. The problem can be fixed by manually inserting the desired function object into the return value of the macro.

(defmacro my-unless (condition &body body)
 `(if (funcall ',#'not ,condition)
    (progn
      ,@body)))

however, this approach can be problematic for code marshaling done by some compilers, and it is not possible to "protect" macro uses this way. (E.g., the "funcall" and the "progn" in the above can be broken too, with no way of protecting them.)

Strategies

In some languages such as Common Lisp, Scheme and others of the Lisp language family, macros provide a powerful means of extending the language. Here the lack of hygiene in conventional macros is resolved by several strategies.

Hygienic macro systems for Scheme

Syntax-rules

Syntax-rules is the standard high-level macro system of R5RS.

 

Syntax-case

Syntax-case is a low- and high-level macro system that is part of R6RS.

 

Syntactic closures

Syntactic closures are another type of macro system.

 

Explicit renaming

Explicit renaming is another type of macro system.

 

References

See also